using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace WPFTemplateLib.Attached
{
	/// <summary>
	/// Grid 附加属性帮助类
	/// </summary>
	public class GridHelper
	{
		#region Rows & Columns

		/*
		 * 在WPF中实现形如RowDefinitions="*,2*,100"的快速声明：https://www.bilibili.com/video/BV12c411b79M/
		 * https://gist.github.com/BYJRK/a6fcc66e63d84dc2a57388400e14032c
		 */

		public static string GetRows(DependencyObject obj)
		{
			return (string)obj.GetValue(RowsProperty);
		}

		public static void SetRows(DependencyObject obj, string value)
		{
			obj.SetValue(RowsProperty, value);
		}

		/// <summary>
		/// 自动为 Grid 控件添加 RowDefinitions
		/// </summary>
		/// <remarks>
		/// 支持的写法包括：<br/>
		/// - Star: 1*, *<br/>
		/// - Auto: auto, Auto<br/>
		/// - Pixel: 100, 10.5, 0
		/// </remarks>
		public static readonly DependencyProperty RowsProperty =
			DependencyProperty.RegisterAttached("Rows", typeof(string), typeof(GridHelper), new FrameworkPropertyMetadata("",
					FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.NotDataBindable,
					OnRowsChanged));

		private static void OnRowsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			if(d is not Grid grid || e.NewValue is not string rows)
				throw new ArgumentException($"Invalid Rows property value: {e.NewValue}");

			grid.RowDefinitions.Clear();

			foreach(var row in rows.Split(',').Select(r => r.Trim()))
			{
				var gridLength = ParseGridLength(row);
				grid.RowDefinitions.Add(new RowDefinition { Height = gridLength });
			}
		}

		public static string GetColumns(DependencyObject obj)
		{
			return (string)obj.GetValue(ColumnsProperty);
		}

		public static void SetColumns(DependencyObject obj, string value)
		{
			obj.SetValue(ColumnsProperty, value);
		}

		/// <summary>
		/// 自动为 Grid 控件添加 ColumnDefinitions。详见 <seealso cref="RowsProperty"/>
		/// </summary>
		public static readonly DependencyProperty ColumnsProperty =
			DependencyProperty.RegisterAttached("Columns", typeof(string), typeof(GridHelper), new FrameworkPropertyMetadata("",
					FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.NotDataBindable,
					OnColumnsChanged));

		private static void OnColumnsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			if(d is not Grid grid || e.NewValue is not string rows)
				throw new ArgumentException($"Invalid Rows property value: {e.NewValue}");

			grid.ColumnDefinitions.Clear();

			foreach(var row in rows.Split(',').Select(r => r.Trim()))
			{
				var gridLength = ParseGridLength(row);
				grid.ColumnDefinitions.Add(new ColumnDefinition { Width = gridLength });
			}
		}

		private static GridLength ParseGridLength(string length)
		{
			// *, 1*, 2.5*
			if(length.EndsWith("*"))
			{
				double star = 1;
				if(length.Length > 1)
					star = double.Parse(length.Substring(0, length.Length - 1));
				return new GridLength(star, GridUnitType.Star);
			}
			// a, auto, A, Auto
			else if(length.Equals("auto", StringComparison.OrdinalIgnoreCase))
			{
				return GridLength.Auto;
			}
			// 100
			else if(double.TryParse(length, out var height))
			{
				return new GridLength(height);
			}

			throw new ArgumentException($"Invalid height value: {length}");
		}

		#endregion

		#region AutoRowColumn

		//https://gist.github.com/BYJRK/66aefb80c838634e0642ffed4f58e076
		//[如何在 WPF 中高效布局多行多列的控件](https://blog.coldwind.top/posts/grid-of-controls/)

		public static string GetAutoRowColumn(DependencyObject obj)
		{
			return (string)obj.GetValue(AutoRowColumnProperty);
		}

		/// <summary>
		/// 自动排列 Grid 容器中的所有控件
		/// </summary>
		/// <remarks>
		/// 值为一个逗号隔开的字符串，形如：<br/>
		///- 2,3（2 行 3 列，列宽默认为 1*，行高为 Auto）<br/>
		///- _,3（3 列，行数根据子控件而定）<br/>
		///- 2,3,Auto（列的宽度为 Auto）<br/>
		/// Grid 中的所有控件将自动按照从左到右、从上到下的顺序进行排列 <br/>
		/// 控件各自的 RowSpan 以及 ColumnSpan 也会被考虑
		/// </remarks>
		/// <example>
		/// <code>
		/// <![CDATA[
		/// <Window xmlns:ap="clr-namespace:NemoDemo.AttachedProperties">
		///     <Grid ap:GridHelper.AutoRowColumn="2,3">
		///         <Button />                     // 1,1
		///         <Label />                      // 1,2
		///         <TextBox />                    // 1,3
		///         <Button />                     // 2,1
		///         <Label Grid.ColumnSpan="2" />  // 2,2-3
		///     </Grid>
		/// </Window>
		/// ]]>
		/// </code>
		/// </example>
		public static void SetAutoRowColumn(DependencyObject obj, string value)
		{
			obj.SetValue(AutoRowColumnProperty, value);
		}

		public static readonly DependencyProperty AutoRowColumnProperty = DependencyProperty.RegisterAttached(
			"AutoRowColumn", typeof(string), typeof(GridHelper),
			new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.AffectsMeasure, OnAutoRowColumnChanged)
		);

		private static void OnAutoRowColumnChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			if(!(d is Grid grid))
				return;

			var value = e.NewValue as string;
			if(string.IsNullOrEmpty(value))
			{
				grid.RowDefinitions.Clear();
				grid.ColumnDefinitions.Clear();

				grid.Loaded -= OnGridLoaded;
				return;
			}

			grid.Loaded += OnGridLoaded;

			if(grid.IsLoaded)
				OnGridLoaded(grid, null);
		}

		private static void OnGridLoaded(object sender, RoutedEventArgs e)
		{
			var grid = sender as Grid;
			if(grid == null) return;

			// 列宽，默认为 Star，即平均分布
			var width = new GridLength(1.0, GridUnitType.Star);
			var split = GetAutoRowColumn(grid).Split(',');
			var r = split[0] != "_" ? int.Parse(split[0]) : 1;
			var c = int.Parse(split[1]);
			// 如果有第三个参数且值为 auto，则宽度为 Auto
			if(split.Length == 3 && split[2].Equals("auto", StringComparison.OrdinalIgnoreCase))
				width = GridLength.Auto;
			grid.RowDefinitions.Clear();
			grid.ColumnDefinitions.Clear();
			for(int i = 0; i < r; i++)
				grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
			for(int i = 0; i < c; i++)
				grid.ColumnDefinitions.Add(new ColumnDefinition { Width = width });

			var cols = grid.ColumnDefinitions.Count;

			var map = new List<bool[]>();

			int x = 0,
				y = 0;
			foreach(UIElement item in grid.Children)
			{
				var rowSpan = Grid.GetRowSpan(item);
				var colSpan = Grid.GetColumnSpan(item);

				// 默认从上到下，从左到右，即任何时候控件的下方和右方都是空的
				// 可能会出现中途有一个 RowSpan > 1 导致其左下方的右侧不为空的情况，暂不处理
				// 同时默认当前的 (x, y) 位置是一个可用位置

				// 当前控件占据的格子
				for(int i = 0; i < rowSpan; i++)
				{
					// 如果 RowDefinition 不够用，则自动添加
					if(map.Count <= y + i)
					{
						while(map.Count <= y + i)
						{
							grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
							map.Add(new bool[cols]);
						}
					}

					map[y + i][x] = true;
				}

				for(int i = 0; i < colSpan; i++)
				{
					if(x + i >= cols)
						break;
					map[y][x + i] = true;
				}

				Grid.SetRow(item, y);
				Grid.SetColumn(item, x);

				// 寻找下一个可用的格子
				while(map[y][x])
				{
					x++;
					if(x >= cols)
					{
						x = 0;
						y++;
						if(y >= map.Count)
						{
							grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
							map.Add(new bool[cols]);
						}
					}
				}
			}
		}

		#endregion

		#region 增强 ShowGridLines

		//[WPF 如何利用附加属性修改 ShowGridLines 效果详解](https://www.jb51.net/article/138238.htm)

		private static void RefreshGrid(Grid grid, int lineWidth, Brush color)
		{
			for(var i = grid.Children.Count - 1; i > 0; i--)
			{
				var child = grid.Children[i];
				var bd = child as Border;
				if(bd != null && bd.Tag != null && bd.Tag.ToString() == "gridline")
				{
					grid.Children.Remove(bd);
				}
			}

			var rows = grid.RowDefinitions.Count;
			var cols = grid.ColumnDefinitions.Count;

			//边界考虑
			if(rows == 0)
			{
				rows = 1;
			}

			if(cols == 0)
			{
				cols = 1;
			}

			//生成行列
			for(var i = 0; i < rows; i++)
			{
				for(var j = 0; j < cols; j++)
				{
					var thick = new Thickness(lineWidth, lineWidth, 0, 0);
					var margin = new Thickness(-lineWidth / 2d, -lineWidth / 2d, 0, 0);

					//边界考虑
					if(i == 0)
					{
						margin.Top = 0;
					}

					if(i == rows - 1)
					{
						thick.Bottom = lineWidth;
					}

					if(j == 0)
					{
						margin.Left = 0;
					}

					if(j == cols - 1)
					{
						thick.Right = lineWidth;
					}

					var bd = new Border
					{
						BorderThickness = thick,
						Margin = margin,
						BorderBrush = color,
						Tag = "gridline"
					};

					Grid.SetRow(bd, i);
					Grid.SetColumn(bd, j);
					grid.Children.Add(bd);
				}
			}

			grid.InvalidateArrange();
			grid.InvalidateVisual();
		}

		#region 线颜色
		public static readonly DependencyProperty LineColorProperty = 
			DependencyProperty.RegisterAttached("LineColor", typeof(Brush), typeof(GridHelper), 
				new PropertyMetadata(Brushes.Black, LineColorPropertyChanged));

		public static Brush GetLineColor(DependencyObject obj)
		{
			return (Brush)obj.GetValue(LineColorProperty);
		}

		public static void SetLineColor(DependencyObject obj, Brush value)
		{
			obj.SetValue(LineColorProperty, value);
		}

		private static void LineColorPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			var grid = d as Grid;
			if(grid == null)
			{
				return;
			}

			var showLines = GetShowGridLines(grid);
			var color = GetLineColor(grid);
			var lineWidth = GetLineWidth(grid);
			if(showLines)
			{
				// grid.SnapsToDevicePixels = true;
				grid.Loaded += delegate
				{
					RefreshGrid(grid, lineWidth, color);
				};
			}
		}
		#endregion

		#region 线宽度 
		public static readonly DependencyProperty LineWidthProperty = 
			DependencyProperty.RegisterAttached("LineWidth", typeof(int), typeof(GridHelper),
				new PropertyMetadata(1, LineWidthPropertyChanged));

		public static int GetLineWidth(DependencyObject obj)
		{
			return (int)obj.GetValue(LineWidthProperty);
		}

		public static void SetLineWidth(DependencyObject obj, int value)
		{
			obj.SetValue(LineWidthProperty, value);
		}

		private static void LineWidthPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			var grid = d as Grid;
			if(grid == null)
			{
				return;
			}

			var showLines = GetShowGridLines(grid);
			var color = GetLineColor(grid);
			var lineWidth = GetLineWidth(grid);
			if(showLines)
			{
				// grid.SnapsToDevicePixels = true;
				grid.Loaded += delegate
				{
					RefreshGrid(grid, lineWidth, color);
				};
			}
		}
		#endregion

		#region 是否显示线
		public static readonly DependencyProperty ShowGridLinesProperty = 
			DependencyProperty.RegisterAttached("ShowGridLines", typeof(bool), typeof(GridHelper), 
				new PropertyMetadata(false, ShowGridLinesPropertyChanged));

		public static bool GetShowGridLines(DependencyObject obj)
		{
			return (bool)obj.GetValue(ShowGridLinesProperty);
		}

		public static void SetShowGridLines(DependencyObject obj, bool value)
		{
			obj.SetValue(ShowGridLinesProperty, value);
		}

		private static void ShowGridLinesPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			var grid = d as Grid;
			if(grid == null)
			{
				return;
			}

			var showLines = GetShowGridLines(grid);
			var color = GetLineColor(grid);
			var lineWidth = GetLineWidth(grid);
			if(showLines)
			{
				// grid.SnapsToDevicePixels = true;
				grid.Loaded += delegate
				{
					RefreshGrid(grid, lineWidth, color);
				};
			}
		}
		#endregion

		#endregion
	}
}
